/*
 * Copyright (C) 2010-2014 Enrique Lara (enrique.posix2.lara@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package app.transittool;

import android.app.Activity;
import android.app.ProgressDialog;

import android.content.Context;

import android.os.Bundle;

import android.view.ContextMenu;

import android.view.ContextMenu.ContextMenuInfo;

import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import app.transittool.model.Agency;
import app.transittool.model.Alarm;
import app.transittool.model.App;
import app.transittool.model.DataRetrievalException;
import app.transittool.model.Matcher;
import app.transittool.model.Stop;
import app.transittool.model.Vehicle;

import app.transittool.provider.Tables.Alarms;

import app.transittool.util.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.TreeSet;

/**
 * @author elara
 *  XXX consider matching on any of the user's alarms for a given stop.
 *  XXX consider refreshing independent of user action.
 *  XXX view stops from current location to destination for a particular trip?
 *       include previous stops but "#snap" to location from this activity.
 */
public final class StopViewer extends Activity {
    private static final String TAG = "StopViewer";
    public static final int MENU_ITEM_INSERT = Menu.FIRST;
    public static final int MENU_ITEM_VIEW = Menu.FIRST + 1;
    public static final int MENU_ITEM_VIEW_NEARBY_STOPS = Menu.FIRST + 2;
    private static final long REFRESH_INTERVAL_MILLIS = 20000;
    private static final int ON_RESUME_REFRESH_DELAY_MILLIS = 250;
    private static final int DEFAULT_MIN_TIME = 1;
    private static final int DEFAULT_MAX_TIME = 60;
    private Stop s = null;
    private Agency agency = null;
    private Matcher matcher = null;
    private LayoutInflater layoutInflater;
    private VehicleAdapter adapter = null;
    private ListView vehiclesView = null;
    private long lastRefreshedTimeMillis = -1;
    private TextView lastRefreshedView;
    private ProgressDialog loadingDialog = null;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.stop_view);

        //
        if (getIntent().getData() != null) {
            final Alarm a = App.loadAlarm(getIntent().getData(), this);
            matcher = new Matcher(a);

            agency = a.getAgency();

            try {
                s = agency.getStopByCode(a.getStopCode());
            } catch (final DataRetrievalException e) {
                Android.showLongToast(this, R.string.could_not_locate_stop, a.getStopCode());
                Log.w(TAG, "Problems obtaining data for stop", e);
            }
        } else {
            final int agencyId = getIntent().getExtras().getInt(Alarms.AGENCY_ID);
            final String apiCode = getIntent().getExtras().getString(Alarms.STOP);

            matcher = null;
            agency = App.getAgency(agencyId, this);

            try {
                s = agency.getStopByCode(apiCode);
            } catch (final DataRetrievalException e) {
                Android.showLongToast(this, R.string.could_not_locate_stop, apiCode);
                Log.w(TAG, "Problems obtaining data for stop", e);
            }
        }

        initButtons();

        //
        final TextView stationView = (TextView) findViewById(R.id.station_name);

        if (s == null) {
            stationView.setText(R.string.unknown);

            return;
        } else {
            stationView.setText(s.getName());
        }

        vehiclesView = (ListView) findViewById(R.id.vehicles);
        vehiclesView.setOnCreateContextMenuListener(this);

        adapter = new VehicleAdapter();
        vehiclesView.setAdapter(adapter);

        lastRefreshedView = (TextView) findViewById(R.id.last_refreshed);

        final View v = findViewById(R.id.view_station_on_map);
        v.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    viewStationOnMap(StopViewer.this);
                }
            });
    }

    private void initButtons() {
        Button b = (Button) findViewById(R.id.done);
        b.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    done();
                }
            });
        b = (Button) findViewById(R.id.refresh);
        b.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(final View v) {
                    refreshAsync(0);
                }
            });
    }

    @Override
    protected void onResume() {
        super.onResume();

        layoutInflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        loadingDialog = new ProgressDialog(this);
        loadingDialog.setMessage(getText(R.string.loading));
        loadingDialog.setIndeterminate(true);
        loadingDialog.setCancelable(true);

        loadingDialog.show();

        refreshAsync(ON_RESUME_REFRESH_DELAY_MILLIS);
    }

    private void done() {
        setResult(RESULT_OK);
        finish();
    }

    protected void refreshAsync(final long delayTimeMillis) {
        final Thread t = new Thread() {
            @Override
            public void run() {
                if (delayTimeMillis > 0) {
                    // Sleep a short spell before refreshing.
                    try {
                        Thread.sleep(delayTimeMillis);
                    } catch (final InterruptedException e) {
                        App.showError(StopViewer.this, "Problems delaying refresh task.", e);
                    }
                }

                refresh();
            }
        };

        t.start();
    }

    private void refresh() {
        if (s == null) {
            Log.d(TAG, "Station is null.");

            return;
        }

        synchronized (lastRefreshedView) {
            if ((System.currentTimeMillis() - lastRefreshedTimeMillis) < REFRESH_INTERVAL_MILLIS) {
                Log.d(TAG, "Last refresh within " + REFRESH_INTERVAL_MILLIS + " ms.");

                return;
            }

            try {
                final Date lastRefreshed = new Date();
                lastRefreshedTimeMillis = lastRefreshed.getTime();

                final ArrayList<Vehicle> vehicles = agency.getVehicles(s);

                final HashMap<Integer, TreeSet<Vehicle>> groups = new HashMap<Integer, TreeSet<Vehicle>>();

                for (final Vehicle v : vehicles) {
                    final Integer groupId = Integer.valueOf(v.getGroup());

                    if (!groups.containsKey(groupId)) {
                        final TreeSet<Vehicle> group = new TreeSet<Vehicle>(new Vehicle.VehicleComparator());
                        groups.put(groupId, group);
                    }

                    groups.get(groupId).add(v);
                }

                StopViewer.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            // Update Last Refreshed Timestamp.
                            lastRefreshedView.setText(Android.formatDate(StopViewer.this, lastRefreshed));

                            adapter.setItems(groups.values());
                            adapter.notifyDataSetChanged();
                            loadingDialog.hide();
                        }
                    });
            } catch (final DataRetrievalException e) {
                App.showError(this, "Problems obtaining vehicle data", e);
            }
        }
    }

    public void viewStationOnMap(final Activity c) {
        if (Settings.isUseStreetView(c)) {
            Android.gotoStreetView(c, s.getLat(), s.getLon());
        } else {
            Android.gotoMap(c, s.getLat(), s.getLon());
        }
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        super.onCreateOptionsMenu(menu);

        menu.add(0, MENU_ITEM_VIEW_NEARBY_STOPS, 0, R.string.nearby_stops).setShortcut('5', 'n').setIcon(android.R.drawable.ic_menu_search);

        return true;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        switch (item.getItemId()) {
            case MENU_ITEM_VIEW_NEARBY_STOPS:
                return s.gotoViewNearbyStops();

            default:
        }

        return super.onOptionsItemSelected(item);
    }

    private class VehicleAdapter extends BaseAdapter {
        private final ArrayList<Object> items = new ArrayList<Object>();

        public VehicleAdapter() {
        }

        public void setItems(final Collection<TreeSet<Vehicle>> groups) {
            items.clear();

            for (final TreeSet<Vehicle> group : groups) {
                items.add("DIVIDER");
                items.addAll(group);
            }
        }

        @Override
        public int getCount() {
            return items.size();
        }

        @Override
        public Object getItem(final int position) {
            return items.get(position);
        }

        @Override
        public long getItemId(final int position) {
            return position;
        }

        @Override
        public View getView(final int position, final View v, final ViewGroup parent) {
            // XXX attempt to reuse view parameter if possible.
            if ("DIVIDER".equals(getItem(position))) {
                final View view = layoutInflater.inflate(agency.getStopDividerViewId(), parent, false);

                return view;
            }

            final View view = layoutInflater.inflate(agency.getStopListItemViewId(), parent, false);
            final Vehicle t = (Vehicle) getItem(position);

            // Headsign
            String destinationName = t.getDestinationName();

            if ((matcher != null) && matcher.isInRange(t)) {
                destinationName = "*" + destinationName + "*";
            }

            Android.cptxt(view, R.id.headsign, destinationName);

            // Minutes
            if (t.getMinutes() != 0) {
                Android.cptxt(view, R.id.minutes, Integer.toString(t.getMinutes()));
            } else {
                Android.cptxt(view, R.id.minutes, t.getStatus());
            }

            // Route
            Android.cproute(view, R.id.line, t.getRoute());

            return view;
        }
    }

    @Override
    public void onCreateContextMenu(final ContextMenu menu, final View view, final ContextMenuInfo menuInfo) {
        AdapterView.AdapterContextMenuInfo info;

        try {
            info = (AdapterView.AdapterContextMenuInfo) menuInfo;
        } catch (final ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);

            return;
        }

        if ("DIVIDER".equals(vehiclesView.getAdapter().getItem(info.position))) {
            return;
        }

        final Vehicle t = (Vehicle) vehiclesView.getAdapter().getItem(info.position);

        if (t == null) {
            // For some reason the requested item isn't available, do nothing
            return;
        }

        menu.setHeaderTitle(t.getDestinationName());
        // menu.add(0, MENU_ITEM_VIEW, 0, R.string.view_destination);
        menu.add(0, MENU_ITEM_INSERT, 0, R.string.add_alarm_for_station);

        //XXX consider enabling nearby and view when we can identify the actual destination (see below).
//        menu.add(0, MENU_ITEM_VIEW_NEARBY_STOPS, 0, R.string.nearby_stops).setShortcut('5', 'n').setIcon(android.R.drawable.ic_menu_search);
    }

    @Override
    public boolean onContextItemSelected(final MenuItem item) {
        AdapterView.AdapterContextMenuInfo info;

        try {
            info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        } catch (final ClassCastException e) {
            Log.e(TAG, "bad menuInfo", e);

            return false;
        }

        if ("DIVIDER".equals(vehiclesView.getAdapter().getItem(info.position))) {
            return false;
        }

        final Vehicle t = (Vehicle) vehiclesView.getAdapter().getItem(info.position);

        switch (item.getItemId()) {
            // case MENU_ITEM_VIEW:
            // XXX destinatoinCode == apiCode for Train. Not so for Bus
            // return agency.gotoViewStop(t.getDestinationCode(), null);
            case MENU_ITEM_INSERT: {
                final Alarm a = t.createMatchingAlarm();
                a.setStop(s.getCode());
                a.setMaxTime(nearestMaxTime(a.getMaxTime()));
                a.setMinTime(nearestMinTime(a.getMinTime()));
                a.updateQuiet();

                return a.gotoEdit();
            }

//            case MENU_ITEM_VIEW_NEARBY_STOPS: {
//                t.getDestinationCode(); //XXX WMATA train gives a usable code.  Bus gives a direction.
            // Any chance we can find match via headsign?
//            }
            default:
                return false;
        }
    }

    private int nearestMinTime(final int minTime) {
        final String[] times = this.getResources().getStringArray(R.array.min_times);

        int lastTime = DEFAULT_MIN_TIME;

        for (int i = 0; i < times.length; i++) {
            final int currentTime = Integer.valueOf(times[i]);

            if (currentTime > minTime) {
                return lastTime;
            }

            lastTime = currentTime;
        }

        return DEFAULT_MIN_TIME;
    }

    private int nearestMaxTime(final int maxTime) {
        final String[] times = this.getResources().getStringArray(R.array.max_times);

        int lastTime = DEFAULT_MAX_TIME;

        for (int i = (times.length - 1); i >= 0; i--) {
            final int currentTime = Integer.valueOf(times[i]);

            if (currentTime < maxTime) {
                return lastTime;
            }

            lastTime = currentTime;
        }

        return DEFAULT_MAX_TIME;
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (loadingDialog != null) {
            loadingDialog.hide();
        }
    }
}
